﻿using Fishing.Abstractions;
using Fishing.Common;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;

namespace Fishing.ViewModel
{
    public class UpdaterViewModel : ObservableObject
    {
        private readonly Task _updateTask;

        private bool _look = false;
        private IncreaseMode _increaseMode;

        private readonly ISettingsService _settingsService;

        private bool _spaceKeyDown = false;
        private bool _longPress = false;

        private Task _longPressPerceptionTask;
        private CancellationTokenSource _cts = new();

        public event EventHandler UpdateCompleted;

        public UpdaterViewModel()
        {
            _updateTask = new Task(async () =>
            {
                while (_progress != 100)
                {
                    if (_cts.IsCancellationRequested)
                    {
                        break;
                    }

                    await Task.Delay(Random.Shared.Next(2000, 10000));

                    if (_look)
                    {
                        continue;
                    }

                    AddPercent();
                }
            }, _cts.Token);

            FontSize = Convert.ToInt32(SystemParameters.PrimaryScreenWidth / 12 / 3 * 3 / 6 * 0.9);

            _settingsService = App.Services.GetRequiredService<ISettingsService>();
        }

        private int _progress;
        public int Progress
        {
            get => _progress;
            set
            {
                _progress = value;

                if (_progress == 100)
                {
#if RELEASE
                    Win32.LockWorkStation();
#endif                    
                    UpdateCompleted(this, EventArgs.Empty);
                }

                OnPropertyChanged();
            }
        }

        private int _fontSize;
        public int FontSize
        {
            get => _fontSize;
            set
            {
                _fontSize = value;
                OnPropertyChanged();
            }
        }

        private Command _addPercentCommand;
        public Command AddPercentCommand
        {
            get
            {
                if (_addPercentCommand is null)
                {
                    _addPercentCommand = new Command(
                        parms => PreviewAddPercent());
                }

                return _addPercentCommand;
            }
        }

        public void Begin()
        {
            Progress = Random.Shared.Next(8, 18);

            _updateTask.Start();
        }

        public void Stop()
        {
            _cts.Cancel();
        }

        public void SetLongPressFail()
        {
            _cts.Cancel();
            _cts = new CancellationTokenSource();

            _spaceKeyDown = false;
            _longPress = false;
        }

        private void PreviewAddPercent()
        {
            if (_longPressPerceptionTask is not null &&
                _longPressPerceptionTask.Status is TaskStatus.Running or TaskStatus.WaitingToRun)
            {
                _cts.Cancel();
                _cts = new CancellationTokenSource();
            }

            _spaceKeyDown = true;

            _increaseMode = _longPress ? IncreaseMode.Single : _settingsService.Settings.IncreaseMode;

            AddPercent();

            _longPressPerceptionTask = Task.Run(async () =>
            {
                await Task.Delay(1000);

                if (_spaceKeyDown)
                {
                    _longPress = true;
                }
            }, _cts.Token);
        }

        private void AddPercent()
        {
            if (_look)
            {
                return;
            }

            var remain = 100 - Progress;

            if (remain < 1)
            {
                return;
            }

            var increment = Random.Shared.Next(1, Random.Shared.Next(1, remain / 2 + 1));

            var animationKind = _increaseMode == IncreaseMode.Single
                ? _settingsService.Settings.IncreaseAlgorithm
                : (IncreaseAlgorithm)Random.Shared.Next(0, 3);

            switch (animationKind)
            {
                case IncreaseAlgorithm.Direct:
                    Progress += increment;
                    break;
                case IncreaseAlgorithm.Linear:
                    AddPercentByLinear(increment);
                    break;
                case IncreaseAlgorithm.Easing:
                    AddPercentByEasing(increment);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private async void AddPercentByLinear(int increment)
        {
            _look = true;

            var ratio = Random.Shared.NextDouble();

            var time = ratio < 0.5 ? ratio + 0.5 : ratio * Random.Shared.Next(1, 3);

            var timeIncrement = time / increment;

            for (var i = 0; i < increment; i++)
            {
                try
                {
                    Dispatcher.CurrentDispatcher.VerifyAccess();

                    await Task.Delay(Convert.ToInt32(timeIncrement * 1000));

                    Progress++;
                }
                catch (Exception)
                {
                    // ignored
                }
            }

            _look = false;
        }

        private async void AddPercentByEasing(int increment)
        {
            _look = true;

            var easingFunction = new ProgressEasing() { EasingMode = EasingMode.EaseIn };

            for (var i = 0; i < increment; i++)
            {
                try
                {
                    Dispatcher.CurrentDispatcher.VerifyAccess();

                    await Task.Delay(Convert.ToInt32(easingFunction.Ease((i + 1d) / increment) * 1000));

                    Progress++;
                }
                catch (Exception)
                {
                    // ignored
                }
            }

            _look = false;
        }

        private class ProgressEasing : EasingFunctionBase
        {
            protected override Freezable CreateInstanceCore()
            {
                return new ProgressEasing();
            }

            protected override double EaseInCore(double normalizedTime)
            {
                normalizedTime = Math.Max(0.0, Math.Min(1.0, normalizedTime));

                return 1.0 - Math.Sqrt(1.0 - normalizedTime * normalizedTime);
            }
        }
    }
}